package com.boyinet.demo.pipelineleakage.service;

import com.boyinet.demo.pipelineleakage.bean.primary.SchedulerJob;
import com.boyinet.demo.pipelineleakage.common.R;
import com.boyinet.demo.pipelineleakage.repository.primary.SchedulerJobRepository;
import com.boyinet.demo.pipelineleakage.util.SchedulerUtils;
import com.boyinet.demo.pipelineleakage.vo.SchedulerJobVo;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SchedulerJobService {

    private static final String CRON_EXPRESSION_TEMPLATE = "{0} 0/{1} * * * ?";

    private final SchedulerJobRepository schedulerJobRepository;

    public SchedulerJobService(SchedulerJobRepository schedulerJobRepository) {
        this.schedulerJobRepository = schedulerJobRepository;
    }

    public R<List<SchedulerJob>> findAll(PageRequest pageRequest) {
        List<SchedulerJob> list = new ArrayList<>(pageRequest.getPageSize());
        long count = schedulerJobRepository.count();
        Iterable<SchedulerJob> all = schedulerJobRepository.findAll(null, pageRequest);
        all.forEach(list::add);
        R<List<SchedulerJob>> ok = R.ok(list);
        ok.setTotal(count);
        ok.setPage(pageRequest);
        return ok;
    }

    public void reloadJobs() {
        log.info("[初始化装载所有定时任务]");
        List<SchedulerJob> schedulerJobs = schedulerJobRepository.findSchedulerJobByStatusEquals(SchedulerJobVo.ON);
        schedulerJobs.forEach(schedulerJob -> {
            try {
                createJob(schedulerJob);
            } catch (SchedulerException e) {
                log.warn("[初始装载Job]，{}:{}装载失败，异常信息：{}", schedulerJob.getJobGroup(), schedulerJob.getJobName(), e.getMessage(), e);
            }
        });
    }

    public void save(SchedulerJobVo vo) throws SchedulerException {
        log.info("[定时任务添加],任务详情：{}", vo);
        boolean create = false;
        SchedulerJob schedulerJob = new SchedulerJob();
        BeanUtils.copyProperties(vo, schedulerJob);
        schedulerJobRepository.save(schedulerJob);
        if (SchedulerJobVo.ON.equals(vo.getStatus())) {
            create = true;
        } else {
            schedulerJob.setStatus(SchedulerJobVo.OFF);
        }
        if (create) {
            createJob(schedulerJob);
        }
    }

    private void createJob(SchedulerJob job) throws SchedulerException {
        if (job.getRunType() == 1) {
            SchedulerUtils.createSimpleJob(job.getJobName(), job.getJobGroup(), job.getJobInterval());
        } else {
            SchedulerUtils.createCronJob(job.getJobName(), job.getJobGroup(), createCornExpression(job.getRunType(), job.getJobInterval()));
        }
    }

    private void updateJob(SchedulerJob job) throws SchedulerException {
        if (job.getRunType() == 1) {
            SchedulerUtils.updateInterval(job.getJobName(), job.getJobGroup(), job.getJobInterval());
        } else {
            SchedulerUtils.updateCron(job.getJobName(), job.getJobGroup(), createCornExpression(job.getRunType(), job.getJobInterval()));
        }
    }

    public void update(SchedulerJobVo vo) throws SchedulerException {
        schedulerJobRepository.findById(vo.getId()).ifPresent(old -> {
            SchedulerJob schedulerJob = new SchedulerJob();
            BeanUtils.copyProperties(vo, schedulerJob);
            if (null == vo.getStatus()) {
                schedulerJob.setStatus(SchedulerJobVo.OFF);
                SchedulerUtils.delete(vo.getJobName(), vo.getJobGroup());
            } else {
                try {
                    if (SchedulerUtils.checkExists(vo.getJobName(), vo.getJobGroup()) && vo.getRunType().equals(old.getRunType())) {
                        log.info("[Job Update],只执行更新触发条件操作");
                        updateJob(schedulerJob);
                    } else {
                        log.info("[Job Update],重新构建Job");
                        SchedulerUtils.delete(vo.getJobName(), vo.getJobGroup());
                        createJob(schedulerJob);
                    }
                } catch (SchedulerException e) {
                    throw new RuntimeException(e);
                }
            }
            schedulerJobRepository.save(schedulerJob);
        });
    }

    public void delete(SchedulerJobVo vo) {
        schedulerJobRepository.deleteById(vo.getId());
        SchedulerUtils.delete(vo.getJobName(), vo.getJobGroup());
    }

    private String createCornExpression(Byte runType, Integer interval) {
        if (runType == 1) {
            int second = Calendar.getInstance().get(Calendar.SECOND);
            return MessageFormat.format(CRON_EXPRESSION_TEMPLATE, second + 2, interval);
        }
        return MessageFormat.format(CRON_EXPRESSION_TEMPLATE, 0, interval);
    }

    public void deleteBatch(List<SchedulerJobVo> vos) {
        List<SchedulerJob> jobs = vos.stream().map(vo -> {
                    SchedulerUtils.delete(vo.getJobName(), vo.getJobGroup());
                    SchedulerJob schedulerJob = new SchedulerJob();
                    BeanUtils.copyProperties(vo, schedulerJob);
                    return schedulerJob;
                }
        ).collect(Collectors.toList());
        schedulerJobRepository.deleteAll(jobs);
    }

    public void deleteByPipelineId(Long pipelineId) {
        SchedulerJob schedulerJob = schedulerJobRepository.findSchedulerJobByPipeLineIdEquals(pipelineId);
        if (Objects.nonNull(schedulerJob)) {
            SchedulerUtils.delete(schedulerJob.getJobName(), schedulerJob.getJobGroup());
            schedulerJobRepository.deleteById(schedulerJob.getId());
        }
    }

    public List<Long> listPipelineIds() {
        List<Long> ids = new ArrayList<>();
        Iterable<SchedulerJob> all = schedulerJobRepository.findAll();
        all.forEach(schedulerJob -> ids.add(schedulerJob.getPipeLineId()));
        return ids;
    }
}
